﻿using System;
using System.Collections.Generic;
using System.Linq;
using CcrFlows.Internal.Stages;
using Microsoft.Ccr.Core;

namespace CcrFlows.Core
{
    public class ForkedFlow<T> : ForkedFlow<T, T, T>
    {
        public ForkedFlow() : base((t, pt0, pt1) => { pt0.Post(t); pt1.Post(t); }) { }
        public ForkedFlow(ForkHandler<T, T, T> handler) : base(handler){}
    }


    public class ForkedFlow<TIn, TOut0, TOut1>
    {
        private readonly StageBase head;


        public ForkedFlow(ForkHandler<TIn, TOut0, TOut1> handler)
        {
            this.head = new ForkStage<TIn, TOut0, TOut1>(handler);
        }

        internal ForkedFlow(StageBase head)
        {
            this.head = head;
        }


        public ForkedFlow<TIn, TBranch0, TBranch1> Do<TBranch0, TBranch1>(Func<TOut0, TBranch0> handler0, Func<TOut1, TBranch1> handler1)
        {
            return Do<TBranch0, TBranch1>((i, po) => po.Post(handler0(i)), (i, po) => po.Post(handler1(i)));
        }

        public ForkedFlow<TIn, TBranch0, TBranch1> Do<TBranch0, TBranch1>(SeqHandler<TOut0, TBranch0> handler0, SeqHandler<TOut1, TBranch1> handler1)
        {
            return AppendBranches<TBranch0, TBranch1>(new SeqStage<TOut0, TBranch0>(handler0), new SeqStage<TOut1, TBranch1>(handler1));
        }

        public ForkedFlow<TIn, TBranch0, TBranch1> Do<TBranch0, TBranch1>(SeqHandler<TOut0, TBranch0> handler0, Func<TOut1, TBranch1> handler1)
        {
            return Do<TBranch0, TBranch1>(handler0, (i, po) => po.Post(handler1(i)));
        }

        public ForkedFlow<TIn, TBranch0, TBranch1> Do<TBranch0, TBranch1>(Func<TOut0, TBranch0> handler0, SeqHandler<TOut1, TBranch1> handler1)
        {
            return Do<TBranch0, TBranch1>((i, po) => po.Post(handler0(i)), handler1);
        }

        public ForkedFlow<TIn, TBranch0, TBranch1> Do<TBranch0, TBranch1>(Flow<TOut0, TBranch0> flow0, Flow<TOut1, TBranch1> flow1)
        {
            return AppendBranches<TBranch0, TBranch1>(flow0.Head, flow1.Head);
        }


        public Flow<TIn, TJoinOut> Join<TJoinOut>(Func<TOut0, TOut1, TJoinOut> handler)
        {
            return Join<TJoinOut>((i0, i1, po) => po.Post(handler(i0, i1)));
        }

        public Flow<TIn, TJoinOut> Join<TJoinOut>(JoinHandler<TOut0, TOut1, TJoinOut> handler)
        {
            var leafs = new List<StageBase>(this.head.TransitiveHull);
            var joinStage = new JoinStage<TOut0, TOut1, TJoinOut>(handler);

            if (leafs.Count() == 1)
            {
                leafs[0].ContinueWith(0, joinStage, 0);
                leafs[0].ContinueWith(1, joinStage, 1);
            }
            else
            {
                leafs[0].ContinueWith(0, joinStage, 0);
                leafs[1].ContinueWith(0, joinStage, 1);
            }

            return new Flow<TIn, TJoinOut>(this.head);
        }


        internal StageBase Head
        {
            get { return this.head; }
        }


        public Port<TIn> Input
        {
            get
            {
                return (Port<TIn>)this.head.Input.First();
            }
        }

        public Port<TOut0> Output0
        {
            get
            {
                return (Port<TOut0>)this.head.TransitiveHull.First().Output.First();
            }
        }

        public Port<TOut1> Output1
        {
            get
            {
                return (Port<TOut1>)this.head.TransitiveHull.Last().Output.Last();
            }
        }

        
        private ForkedFlow<TIn, TBranch0, TBranch1> AppendBranches<TBranch0, TBranch1>(StageBase branch0, StageBase branch1)
        {
            var leafs = new List<StageBase>(this.head.TransitiveHull);

            if (leafs.Count() == 1)
                AppendBranchesToFork(leafs, branch0, branch1);
            else
                ConcatenateBranches(leafs, branch0, branch1);

            return new ForkedFlow<TIn, TBranch0, TBranch1>(this.head);
        }

        private void AppendBranchesToFork(IList<StageBase> leafs, StageBase branch0, StageBase branch1)
        {
            leafs[0].ContinueWith(0, branch0);
            leafs[0].ContinueWith(1, branch1);
        }

        private void ConcatenateBranches(IList<StageBase> leafs, StageBase branch0, StageBase branch1)
        {
            leafs[0].ContinueWith(0, branch0);
            leafs[1].ContinueWith(0, branch1);
        }

    }
}
